package org.tinaelle.client.gui.tree;

import org.tinaelle.client.gui.fieldSet.AdTreeNodeDataDetail;
import org.tinaelle.client.utils.DefaultAsyncCallback;
import org.tinaelle.client.utils.TinaelCenterPanel;
import org.tinaelle.client.utils.Utilities;
import org.tinaelle.common.dto.AdTreeDTO;
import org.tinaelle.common.dto.AdTreeNodeDTO;
import org.tinaelle.common.dto.AdTreeNodeDTORecord;
import org.tinaelle.server.service.AdTreeServiceAsync;

import com.allen_sauer.gwt.log.client.Log;
import com.gwtext.client.core.EventObject;
import com.gwtext.client.core.Position;
import com.gwtext.client.data.Node;
import com.gwtext.client.data.NodeTraversalCallback;
import com.gwtext.client.data.Record;
import com.gwtext.client.data.Tree;
import com.gwtext.client.widgets.Button;
import com.gwtext.client.widgets.MessageBox;
import com.gwtext.client.widgets.Panel;
import com.gwtext.client.widgets.event.ButtonListenerAdapter;
import com.gwtext.client.widgets.form.FormPanel;
import com.gwtext.client.widgets.form.TextField;
import com.gwtext.client.widgets.layout.RowLayout;
import com.gwtext.client.widgets.layout.RowLayoutData;
import com.gwtext.client.widgets.menu.BaseItem;
import com.gwtext.client.widgets.menu.Item;
import com.gwtext.client.widgets.menu.Menu;
import com.gwtext.client.widgets.menu.event.BaseItemListenerAdapter;
import com.gwtext.client.widgets.tree.TreeEditor;
import com.gwtext.client.widgets.tree.TreeNode;
import com.gwtext.client.widgets.tree.TreePanel;
import com.gwtext.client.widgets.tree.event.TreePanelListenerAdapter;

/**
 * DOCUMENT ME!
 * 
 * @author $author$
 * @version $Revision$
 */
public class DataTree extends TinaelCenterPanel {

	private Menu	             menu;
	private TreeNode	         root	   = null;
	private TreeNode	         ctxNode;
	private TreePanel	         treePanel;
	private TreeEditor	         treeEditor;
	private FormPanel	         formPanel;
	private Record	             currentRecord;
	private final int	         adTreeId;
	private boolean	             isLoading	= false;
	private AdTreeNodeDataDetail	treeNodeDataDetail;

	/**
	 * DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 */
	@Override
	public String getHelpText() {
		return "<h2>To be done...</h2>";
	} // end getHelpText()

	/**
	 * DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 */
	@Override
	public String getIntro() {
		final StringBuffer intro = new StringBuffer();
		intro.append( "<h2>Tree Management</h2>\n" );
		intro.append( "Use this screen to manage Trees" );

		return intro.toString();
	} // end getIntro()

	/**
	 * Creates a new DataTree object.
	 * 
	 * @param adTreeId
	 *            DOCUMENT ME!
	 */
	public DataTree(final int adTreeId) {
		this.adTreeId = adTreeId;
	} // end DataTree()

	/**
	 * DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 */
	@Override
	public Panel getViewPanel() {
		if ( panel == null ) {
			panel = new Panel();
			panel.setLayout( new RowLayout() );
			panel.add( getTreePanel(), new RowLayoutData( "50%" ) );
			panel.add( getFormPanel(), new RowLayoutData( "50%" ) );
		} // end if

		return panel;
	} // end getViewPanel()

	public TreePanel getTreePanel() {
		treePanel = new TreePanel();
		treePanel.setAutoHeight( true );
		// treePanel.setAutoWidth( true );

		// treePanel.setHeight( 400 );
		treePanel.setTitle( "Editable Tree" );
		treePanel.setAnimate( true );
		treePanel.setEnableDD( true );
		treePanel.setContainerScroll( true );
		treePanel.setRootVisible( true );
		treePanel.setFrame( true );
		treePanel.addListener( new TreePanelListenerAdapter() {
			@Override
			public void onMoveNode(final Tree treePanel, final TreeNode node, final TreeNode oldParent,
			        final TreeNode newParent, final int index) {
				Log.debug( "treePanel::onMoveNode(), isLoading = " + isLoading );

				if ( !isLoading ) {
					final AdTreeNodeDTO changedNode = ( AdTreeNodeDTO ) node.getUserObject();
					final AdTreeNodeDTO newParentNode = ( AdTreeNodeDTO ) newParent.getUserObject();
					changedNode.setSeqNo( index + 1 );

					// In case the parent node is the Root Node....
					if ( newParentNode != null ) {
						changedNode.setParentId( newParentNode.getAdTreeNodeId() );
					} else {
						changedNode.setParentId( 0 );
					}

					saveTreeNode( changedNode, false );
					rebuildNodeSeqNo( oldParent, newParent );
				}
			}

			@Override
			public void onTextChange(final TreeNode node, final String text, final String oldText) {
				Log.debug( "treePanel::onTextChange(), isLoading = " + isLoading );

				if ( !isLoading ) {
					if ( !treePanel.getRootNode().equals( node ) && !text.equals( oldText ) ) {
						final AdTreeNodeDTO changedNode = ( AdTreeNodeDTO ) node.getUserObject();
						changedNode.setName( text );
						saveTreeNode( changedNode, false );
					}
				}
			}
		} );
		root = new TreeNode();
		treePanel.setRootNode( root );

		final TextField field = new TextField();
		field.setSelectOnFocus( true );
		treeEditor = new TreeEditor( treePanel, field );

		treePanel.addListener( new TreePanelListenerAdapter() {
			@Override
			public void onClick(final TreeNode node, final EventObject e) {
				ctxNode = node;
				displayNodeOnForm( node );
				loadNode( node, 1 );
			} // end onClick()

			@Override
			public void onContextMenu(final TreeNode node, final EventObject e) {
				ctxNode = node;
				showContextMenu( node, e );
			} // end onContextMenu()
		} );
		loadTree( adTreeId );

		return treePanel;
	}

	public FormPanel getFormPanel() {
		formPanel = new FormPanel();
		formPanel.setFrame( true );
		formPanel.setLabelAlign( Position.LEFT );
		formPanel.setPaddings( 5 );
		// formPanel.setWidth( 500 );
		// formPanel.setHeight( 400 );

		treeNodeDataDetail = AdTreeNodeDataDetail.getInstance();

		/*
		 * FieldSet fieldSet = new FieldSet(); fieldSet.setLabelWidth( 90 ); fieldSet.setTitle( "Node Details" );
		 * fieldSet.setAutoHeight( true ); fieldSet.setBorder( false );
		 * 
		 * fieldSet.add( new TextField( "Title", "title", 120 ) ); fieldSet.add( new TextField( "Icon", "iconCls", 120 )
		 * ); fieldSet.add( new TextField( "Thumbnail", "thumbnail", 120 ) ); fieldSet.add( new TextField( "Tooltip",
		 * "tooltip", 120 ) ); fieldSet.add( new TextField( "SeqNo", "seqNo", 120 ) ); fieldSet.add(
		 * ListProvider.getInstance().getScreenCombox() ); fieldSet.add( ListProvider.getInstance().getTableCombox() );
		 * fieldSet.add( new TextField( "RecordId", "recordId", 120 ) ); fieldSet.add(
		 * ListProvider.getInstance().getTreeNodeCombox( "parentId", "Parent" ) ); fieldSet.add( new Checkbox( "Active",
		 * "isActive" ) ); fieldSet.add( new DateField( "Created", "created", 120 ) ); fieldSet.add( new TextField(
		 * "Created by", "createdBy", 120 ) ); fieldSet.add( new DateField( "Updated", "updated", 120 ) ); fieldSet.add(
		 * new TextField( "Updated by", "updatedBy", 120 ) );
		 */
		// inner.add( new PaddedPanel( fieldSet, 0, 10, 0, 0 ), new
		// ColumnLayoutData( 0.5 ) );
		final Button resetButton = new Button( "Reset" );
		resetButton.setIconCls( "reset-icon" );
		resetButton.addListener( new ButtonListenerAdapter() {
			@Override
			public void onClick(final Button button, final EventObject e) {
				formPanel.getForm().loadRecord( getCurrentRecord() );
			} // end onClick()
		} );

		final Button newButton = new Button( "New" );
		newButton.setIconCls( "new-icon" );
		newButton.addListener( new ButtonListenerAdapter() {
			@Override
			public void onClick(final Button button, final EventObject e) {
				final TreeNode newFolder = new TreeNode( "New Folder" );
				final Record newRecord = AdTreeNodeDTORecord.getInstance().getNewRecord();
				ctxNode.appendChild( newFolder );

				final AdTreeNodeDTO newNode = AdTreeNodeDTORecord.getInstance().recordToDto( newRecord );

				if ( !ctxNode.equals( treePanel.getRootNode() ) ) {
					newNode.setParentId( ( ( AdTreeNodeDTO ) ctxNode.getUserObject() ).getAdTreeNodeId() );
				}
				newNode.setAdTreeId( adTreeId );
				newFolder.setUserObject( newNode );

				setCurrentRecord( newRecord );
				treePanel.getSelectionModel().select( ctxNode );
				ctxNode = newFolder;
				displayNodeOnForm( ctxNode );
			} // end onClick()
		} );

		final Button saveButton = new Button( "Save" );
		saveButton.setIconCls( "save-icon" );
		saveButton.addListener( new ButtonListenerAdapter() {
			@Override
			public void onClick(final Button button, final EventObject e) {
				formPanel.getForm().updateRecord( getCurrentRecord() );

				final AdTreeNodeDTO treeNode = AdTreeNodeDTORecord.getInstance().recordToDto( getCurrentRecord() );
				saveTreeNode( treeNode, true );
			} // end onClick()
		} );

		final Button deleteButton = new Button( "Delete" );
		deleteButton.setIconCls( "delete-icon" );
		deleteButton.addListener( new ButtonListenerAdapter() {
			@Override
			public void onClick(final Button button, final EventObject e) {
				final AdTreeNodeDTO treeNode = AdTreeNodeDTORecord.getInstance().recordToDto( getCurrentRecord() );
				AdTreeServiceAsync.Util.getInstance().delete( treeNode, new DefaultAsyncCallback<Void>() {
					public void onSuccess(final Void dummy) {
						formPanel.getForm().reset();

						final TreeNode node = treePanel.getNodeById( Integer.toString( treeNode.getAdTreeNodeId() ) );
						node.remove();
					}
				} );
			}
		} );
		formPanel.add( treeNodeDataDetail.getFieldPanel() );
		formPanel.addButton( resetButton );
		formPanel.addButton( newButton );
		formPanel.addButton( saveButton );
		formPanel.addButton( deleteButton );

		// formPanel.add( inner );

		return formPanel;
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param oldParent
	 *            DOCUMENT ME!
	 * @param newParent
	 *            DOCUMENT ME!
	 */
	public void rebuildNodeSeqNo(final TreeNode oldParent, final TreeNode newParent) {
		if ( oldParent == null ) {
			// Rebuild Root Node
			rebuildNodeSeqNo( treePanel.getRootNode() );
		} else {
			rebuildNodeSeqNo( oldParent );
		}

		if ( ( oldParent == null ) && ( newParent == null ) ) {
			// Do nothing, already done
		} else if ( newParent == null ) {
			// Rebuild Root Node
			rebuildNodeSeqNo( treePanel.getRootNode() );
		} else {
			rebuildNodeSeqNo( newParent );
		}
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param node
	 *            DOCUMENT ME!
	 */
	public void rebuildNodeSeqNo(final TreeNode node) {
		if ( node.getChildNodes() == null ) {
			// No children, nothing to do
			return;
		}
		final Node[] children = node.getChildNodes();

		for ( int i = 0; i < children.length; i++ ) {
			final Node t = children[ i ];
			final AdTreeNodeDTO treeNode = ( AdTreeNodeDTO ) t.getUserObject();

			if ( treeNode.getSeqNo() != ( i + 1 ) ) {
				// The seqNo has changed
				treeNode.setSeqNo( i + 1 );

				AdTreeServiceAsync.Util.getInstance().save( treeNode, new DefaultAsyncCallback<AdTreeNodeDTO>() {
					public void onSuccess(final AdTreeNodeDTO treeNodeDTO) {
						// If the current record is our node, update it
						if ( currentRecord.getAsString( "adTreeNodeId" ).equals(
						        treeNodeDTO.getAdTreeNodeId().toString() ) ) {
							setCurrentRecord( AdTreeNodeDTORecord.getInstance().dtoToRecord( treeNodeDTO ) );
							displayNodeOnForm( ( TreeNode ) t );
						}
					} // end onSuccess()
				} );
			}
		}
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param adTreeNodeDTO
	 *            DOCUMENT ME!
	 */
	public void saveTreeNode(final AdTreeNodeDTO adTreeNodeDTO, final boolean updateTreeNode) {
		Log.debug( "saveTreeNode()" );
		AdTreeServiceAsync.Util.getInstance().save( adTreeNodeDTO, new DefaultAsyncCallback<AdTreeNodeDTO>() {
			public void onSuccess(final AdTreeNodeDTO treeNodeDTO) {
				final Record updatedRecord = AdTreeNodeDTORecord.getInstance().dtoToRecord( treeNodeDTO );
				setCurrentRecord( updatedRecord );
				ctxNode.setUserObject( treeNodeDTO );
				displayNodeOnForm( ctxNode );
				if ( updateTreeNode ) {
					addOrUpdateTreeNode( ctxNode, treeNodeDTO );
				}
			} // end onSuccess()

			@Override
			public void onFailure(final Throwable arg0) {
				super.onFailure( arg0 );
			}
		} );
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 */
	public Record getCurrentRecord() {
		if ( currentRecord == null ) {
			MessageBox.alert( "Please select a node first" );

			return null;
		}

		return currentRecord;
	}

	/**
	 * Set the current working record
	 * 
	 * @param currentRecord
	 *            The current working record
	 * 
	 */
	public void setCurrentRecord(final Record currentRecord) {
		this.currentRecord = currentRecord;
	}

	/**
	 * Display the node on the form panel for view/edit
	 * 
	 * @param node
	 *            The tree node to display
	 * 
	 */
	public void displayNodeOnForm(final TreeNode node) {
		if ( ( node.getUserObject() != null ) && ( node.getUserObject() instanceof AdTreeNodeDTO ) ) {
			Log.debug( "displayNodeOnForm() :: Display node on Form : " + node.getText() );

			final AdTreeNodeDTO treeNodeDTO = ( AdTreeNodeDTO ) node.getUserObject();
			setCurrentRecord( AdTreeNodeDTORecord.getInstance().dtoToRecord( treeNodeDTO ) );

			treeNodeDataDetail.preloadCombox( getCurrentRecord() );

			formPanel.getForm().loadRecord( getCurrentRecord() );
		} // end if
	} // end displayNodeOnForm()

	/**
	 * Load a tree in the tree panel
	 * 
	 * @param adTreeId
	 *            The tree to load
	 * 
	 */
	public void loadTree(final int adTreeId) {
		AdTreeServiceAsync.Util.getInstance().getTreeById( new Integer( adTreeId ),
		        new DefaultAsyncCallback<AdTreeDTO>() {
			        public void onSuccess(final AdTreeDTO tree) {
				        Log.debug( "loadTree( " + adTreeId + " ) :: Load Root Node" );
				        final TreeNode rootNode = treePanel.getRootNode();
				        rootNode.setText( tree.getName() );
				        rootNode.setTooltip( tree.getDescription() );
				        rootNode.setUserObject( tree );
				        rootNode.setId( Integer.toString( tree.getAdTreeId() ) );

				        isLoading = true;
				        Log.debug( "loadTree( " + adTreeId + " ) :: Load child nodes" );
				        AdTreeServiceAsync.Util.getInstance().getTreeNodes( new Integer( adTreeId ),
				                new DefaultAsyncCallback<AdTreeNodeDTO[]>() {
					                public void onSuccess(final AdTreeNodeDTO[] treeNodes) {
						                for ( int i = 0; i < treeNodes.length; i++ ) {
							                final TreeNode newNode = addOrUpdateTreeNode( null, treeNodes[ i ] );
							                loadNode( newNode, 0 );
						                } // end for

						                isLoading = false;
					                } // end onSuccess()

					                @Override
					                public void onFailure(final Throwable arg0) {
						                super.onFailure( arg0 );
						                isLoading = false;
					                }
				                } // end new
				                );
			        } // end onSuccess()
		        } // end new
		        );

	} // end loadTree()

	/**
	 * Add or update the nodeDTO into the tree panel. If no treeNode is specified, then it's automatically created and
	 * added at the root node
	 * 
	 * @param treeNode
	 *            Place to add or update the nodeDTO
	 * @param treeNodeDTO
	 *            The nodeDTO to add or update
	 * 
	 * @return The created/updated treeNode
	 */
	public TreeNode addOrUpdateTreeNode(final TreeNode treeNode, final AdTreeNodeDTO treeNodeDTO) {
		TreeNode node;
		Log.debug( "addOrUpdateTreeNode() :: TreeNode : " + ( treeNode == null ? "<Null>" : treeNode.getId() )
		        + ", TreeNodeDTO : " + treeNodeDTO.getAdTreeNodeId() );
		if ( treeNode == null ) {
			node = new TreeNode();

			if ( treeNodeDTO.getParentId() == null ) {
				treePanel.getRootNode().appendChild( node );
				Log.debug( "addOrUpdateTreeNode() :: Add the node to Root Node" );
			} else {
				Log.debug( "addOrUpdateTreeNode() :: lookup node by parent : " + treeNodeDTO.getParentId() );
				final TreeNode parentNode = treePanel.getNodeById( treeNodeDTO.getParentId().toString() );
				if ( parentNode != null ) {
					parentNode.appendChild( node );
					Log.debug( "addOrUpdateTreeNode() :: Add the node to " + parentNode.getText() + " node" );
				} else {
					Log.error( "addOrUpdateTreeNode() :: Parent node not found..." );
				}
			}
		} else {
			node = treeNode;
		}

		Log.debug( "addOrUpdateTreeNode() :: New node ID = " + treeNodeDTO.getAdTreeNodeId() );
		node.setId( treeNodeDTO.getAdTreeNodeId().toString() );

		if ( !Utilities.isBlankOrNull( treeNodeDTO.getName() ) ) {
			// Prevent firing event..
			final boolean isLoadingBackup = isLoading;
			isLoading = true;
			node.setText( treeNodeDTO.getName() );
			isLoading = isLoadingBackup;
		}

		if ( !Utilities.isBlankOrNull( treeNodeDTO.getIconCls() ) ) {
			node.setIconCls( treeNodeDTO.getIconCls() );
		}

		node.setLeaf( treeNodeDTO.getIsLeaf() );
		node.setUserObject( treeNodeDTO );

		return node;
	}

	/**
	 * Load the node from the Database
	 * 
	 * @param node
	 *            The node to retreive from the Database
	 */
	public void loadNode(final TreeNode node, final int deep) {
		if ( !node.isLeaf() && ( node.getChildNodes().length == 0 ) ) {
			Log.debug( "loadNode() :: Load Node : " + node.getText() + " / " + node.getId() );
			isLoading = true;
			AdTreeServiceAsync.Util.getInstance().getNodes( new Integer( node.getId() ),
			        new DefaultAsyncCallback<AdTreeNodeDTO[]>() {
				        public void onSuccess(final AdTreeNodeDTO[] treeNodes) {
					        for ( int i = 0; i < treeNodes.length; i++ ) {
						        final TreeNode newNode = addOrUpdateTreeNode( null, treeNodes[ i ] );
						        node.appendChild( newNode );
						        if ( deep > 0 ) {
							        loadNode( newNode, deep - 1 );
						        }
					        } // end for

					        isLoading = false;
				        } // end onSuccess()

				        @Override
				        public void onFailure(final Throwable arg0) {
					        super.onFailure( arg0 );
					        isLoading = false;
				        }
			        } // end new
			        );
		} // end if
	} // end loadNode()

	private void showContextMenu(final TreeNode node, final EventObject e) {
		if ( menu == null ) {
			menu = new Menu();

			final Item editItem = new Item( "Edit", new BaseItemListenerAdapter() {
				@Override
				public void onClick(final BaseItem item, final EventObject e) {
					treeEditor.startEdit( ctxNode );
				} // end onClick()
			} // end new
			);
			editItem.setId( "edit-item" );
			menu.addItem( editItem );

			final Item disableItem = new Item( "Disable", new BaseItemListenerAdapter() {
				@Override
				public void onClick(final BaseItem item, final EventObject e) {
					ctxNode.disable();
					ctxNode.cascade( new NodeTraversalCallback() {
						public boolean execute(final Node node) {
							( ( TreeNode ) node ).disable();

							return true;
						} // end execute()
					} // end new
					        );
				} // end onClick()
			} // end new
			);
			disableItem.setId( "disable-item" );
			menu.addItem( disableItem );

			final Item enableItem = new Item( "Enable", new BaseItemListenerAdapter() {
				@Override
				public void onClick(final BaseItem item, final EventObject e) {
					ctxNode.enable();
					ctxNode.cascade( new NodeTraversalCallback() {
						public boolean execute(final Node node) {
							( ( TreeNode ) node ).enable();

							return true;
						} // end execute()
					} // end new
					        );
				} // end onClick()
			} // end new
			);
			enableItem.setId( "enable-item" );
			menu.addItem( enableItem );

			final Item cloneItem = new Item( "Clone", new BaseItemListenerAdapter() {
				@Override
				public void onClick(final BaseItem item, final EventObject e) {
					final TreeNode clone = ctxNode.cloneNode();
					final AdTreeNodeDTO nodeDTO = ( AdTreeNodeDTO ) clone.getUserObject();
					nodeDTO.setAdTreeNodeId( 0 );
					clone.setText( "Copy of " + clone.getText() );
					ctxNode.getParentNode().appendChild( clone );
					treeEditor.startEdit( clone );
				} // end onClick()
			} // end new
			);
			cloneItem.setId( "clone-item" );
			menu.addItem( cloneItem );

			final Item newFolderItem = new Item( "New Folder", new BaseItemListenerAdapter() {
				@Override
				public void onClick(final BaseItem item, final EventObject e) {
					final TreeNode newFolder = new TreeNode( "New Folder" );
					final Record newRecord = AdTreeNodeDTORecord.getInstance().getNewRecord();
					ctxNode.appendChild( newFolder );

					final AdTreeNodeDTO newNode = AdTreeNodeDTORecord.getInstance().recordToDto( newRecord );

					if ( !ctxNode.equals( treePanel.getRootNode() ) ) {
						newNode.setParentId( ( ( AdTreeNodeDTO ) ctxNode.getUserObject() ).getAdTreeNodeId() );
					}
					newNode.setAdTreeId( adTreeId );
					newFolder.setUserObject( newNode );

					formPanel.getForm().loadRecord( newRecord );
					setCurrentRecord( newRecord );
					treePanel.getSelectionModel().select( ctxNode );
					ctxNode = newFolder;
					treeEditor.startEdit( newFolder );
				} // end onClick()
			} // end new
			);
			newFolderItem.setId( "newfolder-item" );
			menu.addItem( newFolderItem );

			final Item refreshItem = new Item( "Refresh", new BaseItemListenerAdapter() {
				@Override
				public void onClick(final BaseItem item, final EventObject e) {
					final Node[] children = ctxNode.getChildNodes();
					for ( int i = 0; i < children.length; i++ ) {
						ctxNode.removeChild( children[ i ] );
					}
					if ( !ctxNode.equals( treePanel.getRootNode() ) ) {
						addOrUpdateTreeNode( ctxNode, ( AdTreeNodeDTO ) ctxNode.getUserObject() );
						loadNode( ctxNode, 1 );
					} else {
						loadTree( ( ( AdTreeDTO ) ctxNode.getUserObject() ).getAdTreeId() );
					}

				} // end onClick()
			} // end new
			);
			refreshItem.setId( "refresh-item" );
			menu.addItem( refreshItem );
		} // end if

		if ( ctxNode != null ) {
			ctxNode = null;
		} // end if

		ctxNode = node;

		if ( ctxNode.isDisabled() ) {
			menu.getItem( "disable-item" ).disable();
			menu.getItem( "enable-item" ).enable();
		} // end if
		else {
			menu.getItem( "disable-item" ).enable();
			menu.getItem( "enable-item" ).disable();
		} // end else

		menu.showAt( e.getXY() );
	} // end showContextMenu()
} // end DataTree
